﻿using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CodeActions;
using Microsoft.CodeAnalysis.CodeRefactorings;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using Microsoft.CodeAnalysis.Formatting;
using Microsoft.CodeAnalysis.Text;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;

namespace CodeAnalysisRuleCollection.Common.CSharp
{
    /// <example>public event EventHandler Event;</example>
    [ExportCodeRefactoringProvider("ExpandAutoEvent", LanguageNames.CSharp)]
    internal partial class ExpandAutoEventCodeRefactoringProvider : ICodeRefactoringProvider
    {
        public async Task<IEnumerable<CodeAction>> GetRefactoringsAsync(Document document, TextSpan span, CancellationToken cancellationToken)
        {
            var root = await document.GetSyntaxRootAsync(cancellationToken).ConfigureAwait(false) as CompilationUnitSyntax;

            var @event = root.FindToken(span.Start).Parent.AncestorsAndSelf().OfType<EventFieldDeclarationSyntax>().FirstOrDefault();
            if (@event == null)
                return null;

            return new[] { CodeAction.Create("Expand event declaration", c => ExpandAutoEventAsync(root, document, @event, c)) };
        }

        protected virtual async Task<Document> ExpandAutoEventAsync(SyntaxNode root, Document document, EventFieldDeclarationSyntax @event, CancellationToken c)
        {
            SemanticModel model = await document.GetSemanticModelAsync(c).ConfigureAwait(false);

            // compute the name of the backing field
            string fieldName = GenerateBackingFieldName(@event, model, c);

            // find declaring type
            TypeDeclarationSyntax type = @event.Ancestors().OfType<TypeDeclarationSyntax>().First();

            // add backing field to type
            TypeDeclarationSyntax newType = type
                .WithBackingField(model.GetTypeInfo(@event.Declaration.Type).Type, fieldName, document.Project.Solution.Workspace);

            // find property declaration in new type
            @event = newType.Members.OfType<EventFieldDeclarationSyntax>().Single(m => @event.IsEquivalentTo(m));

            // expand accessors
            var newNode = SyntaxFactory.EventDeclaration(
                @event.AttributeLists,
                @event.Modifiers,
                @event.EventKeyword,
                @event.Declaration.Type,
                null, // TODO: Copy explicit interface implementation syntax
                @event.Declaration.Variables.First().Identifier,
                SyntaxFactory.AccessorList(
                    new SyntaxList<AccessorDeclarationSyntax>()
                        .Add(
                            SyntaxFactory.AccessorDeclaration(SyntaxKind.AddAccessorDeclaration,
                                SyntaxFactory.Block(
                                    SyntaxFactory.ExpressionStatement(
                                        SyntaxFactory.BinaryExpression(SyntaxKind.AddAssignmentExpression,
                                            SyntaxFactory.IdentifierName(fieldName),
                                            SyntaxFactory.IdentifierName("value")
                                        )
                                    )
                                )
                            )
                                .WithSemicolonToken(default(SyntaxToken))
                                .WithTrailingTrivia(default(SyntaxTriviaList))
                        )
                        .Add(
                            SyntaxFactory.AccessorDeclaration(SyntaxKind.RemoveAccessorDeclaration,
                                SyntaxFactory.Block(
                                    SyntaxFactory.ExpressionStatement(
                                        SyntaxFactory.BinaryExpression(SyntaxKind.SubtractAssignmentExpression,
                                            SyntaxFactory.IdentifierName(fieldName),
                                            SyntaxFactory.IdentifierName("value")
                                        )
                                    )
                                )
                            )
                                .WithSemicolonToken(default(SyntaxToken))
                                .WithLeadingTrivia(default(SyntaxTriviaList))
                        )
                )
                    .WithAdditionalAnnotations(Formatter.Annotation)
            );

            // replace nodes
            newType = newType.ReplaceNode<TypeDeclarationSyntax, MemberDeclarationSyntax>(@event, newNode);

            var newRoot = root
                .ReplaceNode(type, newType)
            ;

            return document.WithSyntaxRoot(newRoot);
        }

        private static string GenerateBackingFieldName(EventFieldDeclarationSyntax @event, SemanticModel semanticModel, CancellationToken c)
        {
            // backing field name
            var baseName = @event.Declaration.Variables.First().Identifier.ValueText; // TODO: Handle multiple variables
            baseName = string.Concat('_', char.ToLower(baseName[0]), baseName.Substring(1));

            var eventSymbol = semanticModel.GetDeclaredSymbol(@event, c);
            if (eventSymbol == null ||
                eventSymbol.ContainingType == null)
                return baseName;

            // generate new name with index
            var index = 0;
            var name = baseName;

            while (eventSymbol.ContainingType.MemberNames.Contains(name))
                name = baseName + ++index;

            return name;
        }
    }
}
